package com.example;

import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayParamFlowItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;

import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Spring Cloud Gateway集成Sentinel网关限流
 * Route维度限流
 * <p>
 * 创  建   时  间： 2020/8/15
 * 版           本: V1.0
 * 作           者: qixin
 * 版  权   所  有: 版权所有(C)2019-2029
 * 公           司: 深圳市七智星科技有限公司
 */
@Configuration
public class GatewayConfiguration {

    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers = viewResolvers.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    /**
     * 注入全局限流过滤器：SentinelGatewayFilter
     *
     * @return GlobalFilter
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    /**
     * 注入限流异常处理器
     *
     * @return GpSentinelGatewayBlockExceptionHandler
     */
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GpSentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
        return new GpSentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
    }


    @PostConstruct
    public void doInit() {
        initGatewayRules();
        initCustomizedApis();
    }

    /**
     * 初始化限流规则
     */
    private void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        // 资源名称，可以是网关中的route名称或者用户自定义的API分组名称
        GatewayFlowRule gatewayFlowRule = new GatewayFlowRule("nacos-gateway-provider").
                setCount(1).
                setIntervalSec(1);

        GatewayFlowRule customerFlowRule = new GatewayFlowRule("first_customized_api").
                // 资源模型，限流规则是针对API Gateway的Route（RESOURCE_MODE_ROUTE_ID）还是用户在Sentinel中定义的API分组（RESOURCE_MODE_CUSTOM_API_NAME），默认是route
                        setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME).
                // setGrade() 限流指标维度，同限流规则的grade字段
                // setControlBehavior() 流量整形的控制效果，同限流规则的controlBehavior字段，目前支持快速失败和匀速排队两种模式，默认是快速失败
                // setBurst() 应对突发请求时额外允许的请求数目
                // setMaxQueueingTimeoutMs() 匀速排队模式下最长排队时间，单位是毫秒。仅在匀速排队模式下生效
                // setParamItem() 参数限流配置。若不提供，则代表不针对参数进行限流，该网关规则将会被转换成普通流控规则；否则会转换成热点规则。其中的字段如下：
                // parseStrategy：从请求中提取参数的策略，目前支持提取来源IP（PARAM_PARSE_STRATEGY_CLIENT_IP）、Host（PARAM_PARSE_STRATEGY_HOST）、任意Header（PARAM_PARSE_STRATEGY_HEADER）和任意URL参数（PARAM_PARES_STRATEGY_URL_PARAM）四种模式
                // fieldName：若提取策略选择Header模式或URL参数模式，则需要指定对应的Header名称或URL参数名称
                // pattern和matchStrategy：为后续参数匹配特性预留，目前未实现
                        setCount(1). // 限流阈值
                setIntervalSec(1); // 统计时间窗口，单位是秒，默认是1秒

        rules.add(gatewayFlowRule);
        rules.add(customerFlowRule);
        // 网关限流规则的加载可以通过GatewayRuleManager.loadRules(rules);的方式手动加载，也可以通过
        // GatewayRuleManager.register2Property(property);注册动态限流规则（建议使用这种动态限流规则的方式）
        GatewayRuleManager.loadRules(rules);
    }

    /**
     * 自定义API分组限流
     */
    private void initCustomizedApis() {
        Set<ApiDefinition> definitions = new HashSet<>();
        ApiDefinition apiDefinition = new ApiDefinition("first_customized_api");
        apiDefinition.setPredicateItems(new HashSet<ApiPredicateItem>() {{
            add(new ApiPathPredicateItem().setPattern("/foo/**"));
            add(new ApiPathPredicateItem().setPattern("/baz/**").setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
        }});
        definitions.add(apiDefinition);
        GatewayApiDefinitionManager.loadApiDefinitions(definitions);
    }
}
